package net.towee.client.com.repo.view;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import net.towee.client.com.repo.presenter.RepositoryDocumentPresenter.Display;
import net.towee.client.i18n.ToweeConstants;
import net.towee.client.resources.ClientResource;
import net.towee.client.widgets.DataHelpers;
import net.towee.client.widgets.LoadingBar;
import net.towee.client.widgets.SpanPanel;
import net.towee.model.ProfileArea;
import net.towee.model.repo.AttachmentIdentity;
import net.towee.model.repo.AttachmentInfo;
import net.towee.model.repo.DocumentData;
import net.towee.model.repo.DocumentDiff;
import net.towee.model.utils.CharBase64;

import com.google.gwt.animation.client.Animation;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.HasClickHandlers;
import com.google.gwt.event.dom.client.MouseOutEvent;
import com.google.gwt.event.dom.client.MouseOutHandler;
import com.google.gwt.event.dom.client.MouseOverEvent;
import com.google.gwt.event.dom.client.MouseOverHandler;
import com.google.gwt.event.logical.shared.HasOpenHandlers;
import com.google.gwt.event.logical.shared.HasSelectionHandlers;
import com.google.gwt.event.logical.shared.OpenEvent;
import com.google.gwt.event.logical.shared.OpenHandler;
import com.google.gwt.event.logical.shared.SelectionEvent;
import com.google.gwt.event.logical.shared.SelectionHandler;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.HasWidgets;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.PopupPanel;
import com.google.gwt.user.client.ui.Widget;
import com.google.inject.Inject;

public class RepositoryDocumentView extends FlowPanel implements Display {
	private class HideAnimation extends Animation {
		private boolean showed = false;

		@Override
		public void onComplete() {
			super.onComplete();
			this.showed = !showed;
		}

		@Override
		public void onStart() {
			super.onStart();

			if (!revSelector.isVisible())
				revSelector.setVisible(true);
		}

		@Override
		protected void onUpdate(double progress) {
			progress = showed ? 1 - progress : progress;
			revSelector.getElement().getStyle().setHeight(30. * progress, Unit.PX);
		}
	}

	private class RevisionSelector extends FlowPanel implements HasSelectionHandlers<Integer>,
			MouseOutHandler, MouseOverHandler, ClickHandler {
		private List<Label> revisions;
		private Label activeRevision;
		private HideAnimation animation = new HideAnimation();

		private PopupPanel infoPopup;
		private SpanPanel authorName;
		private FlowPanel dateContainer;
		private SpanPanel expl;

		public RevisionSelector() {
			setStyleName(resource.css().repoDocumentRevContainer());
			this.revisions = new LinkedList<Label>();
			setVisible(false);

			createPopup();
			createOriginalRevision();
		}

		private void createPopup() {
			infoPopup = new PopupPanel(true);
			infoPopup.setAutoHideEnabled(true);
			infoPopup.setGlassEnabled(false);
			infoPopup.setStyleName(resource.css().repoDocumentExplPopup());

			authorName = new SpanPanel();
			dateContainer = new FlowPanel();
			expl = new SpanPanel();

			authorName.getElement().setAttribute("author", "");
			dateContainer.getElement().setAttribute("date", "");
			expl.getElement().setAttribute("expl", "");

			FlowPanel container = new FlowPanel();
			container.add(authorName);
			container.add(dateContainer);
			container.add(expl);

			infoPopup.setWidget(container);
		}

		private void createOriginalRevision() {
			// Create first revision
			Label rev = new Label(constants.original());
			rev.getElement().setAttribute("revision", "first");
			rev.getElement().setAttribute("active", "");
			rev.setStyleName("");
			add(rev);

			// Set handlers
			rev.addMouseOverHandler(this);
			rev.addMouseOutHandler(this);
			rev.addClickHandler(this);

			// Set current revision
			activeRevision = rev;
		}

		public void addRevision(DocumentDiff diff) {
			// Create label
			Label rev = new Label();
			rev.setLayoutData(diff);
			rev.getElement().setAttribute("revision", "");
			rev.setStyleName("");
			add(rev);

			// Set handlers
			rev.addMouseOverHandler(this);
			rev.addMouseOutHandler(this);
			rev.addClickHandler(this);

			// Add to list
			revisions.add(rev);
			rev.setText((revisions.size() - 1) + "");
			selectRevision(revisions.size() - 1);
		}

		@Override
		public void clear() {
			super.clear();
			revisions.clear();
			createOriginalRevision();
		}

		public void selectRevision(int revision) {
			activeRevision.getElement().removeAttribute("active");

			if (revision < 0)
				activeRevision = (Label) getWidget(0);
			else
				activeRevision = revisions.get(revision);

			activeRevision.getElement().setAttribute("active", "");
		}

		public void show() {
			animation.run(350);
		}

		@Override
		public void onMouseOver(MouseOverEvent event) {
			Widget source = (Widget) event.getSource();

			// Set data
			if (source.getLayoutData() != null) {
				// Set popup position
				infoPopup.setPopupPosition(source.getAbsoluteLeft() + (source.getOffsetHeight() / 2)
						- 125, source.getAbsoluteTop() + 40);

				// Get diff info
				DocumentDiff diff = (DocumentDiff) source.getLayoutData();
				authorName.setText(diff.getAuthor().getFullName());
				dateContainer.clear();
				dateContainer.add(DataHelpers.formattedDateTimeWidget(diff.getCreationDate()));
				expl.setText(diff.getExplanation());

				// Show popup
				infoPopup.show();
			}
		}

		@Override
		public void onMouseOut(MouseOutEvent event) {
			infoPopup.hide();
		}

		@Override
		public void onClick(ClickEvent event) {
			Object source = event.getSource();
			int rev = -1;

			if (revisions.contains(source))
				rev = revisions.indexOf(source);

			SelectionEvent.fire(this, rev);
		}

		@Override
		public HandlerRegistration addSelectionHandler(SelectionHandler<Integer> handler) {
			return addHandler(handler, SelectionEvent.getType());
		}
	}

	private class AttachmentsContainer extends FlowPanel implements HasOpenHandlers<AttachmentIdentity>,
			ClickHandler, ValueChangeHandler<Boolean> {
		private final Button downAll;
		private final Button downSelected;

		private final List<String> selectedFiles;
		private final FlowPanel filesContainer;

		public AttachmentsContainer() {
			// Create blocks
			downAll = new Button(constants.download_all());
			downSelected = new Button(constants.download_selected());
			selectedFiles = new ArrayList<String>();
			filesContainer = new FlowPanel();

			// Set style
			setStyleName(resource.css().repoDocumentAttContainer());

			createButtons();
			createFilesContaier();
		}

		@Override
		public void clear() {
			filesContainer.clear();
			selectedFiles.clear();
		}

		private void createFilesContaier() {
			filesContainer.setStyleName(resource.css().repoDocumentFilesContainer());
			add(filesContainer);
		}

		private void createButtons() {
			FlowPanel buttons = new FlowPanel();
			buttons.add(downAll);
			buttons.add(downSelected);
			add(buttons);

			downAll.setStyleName(resource.css().repoDocumentDownAll());
			downSelected.setStyleName(resource.css().repoDocumentDownSelected());

			downAll.addClickHandler(this);
			downSelected.addClickHandler(this);
		}

		public void addAttachment(AttachmentIdentity att) {
			// Create fields
			CheckBox checkBox = new CheckBox();
			Anchor fileLink = new Anchor(att.getName());
			SpanPanel downloads = new SpanPanel(" (" + att.getDownloads() + ")");
			SpanPanel size = new SpanPanel(DataHelpers.humanReadableByteCount(att.getSize()));

			// Set link
			checkBox.setLayoutData(att);
			fileLink.setLayoutData(att);
			fileLink.addClickHandler(this);

			// Pack fields to container
			SpanPanel container = new SpanPanel();
			container.add(checkBox);
			container.add(fileLink);
			container.add(downloads);
			container.add(size);

			FlowPanel realContainer = new FlowPanel();
			realContainer.add(container);
			filesContainer.add(realContainer);

			// Set chekbox handler
			checkBox.addValueChangeHandler(this);

			// Styles
			realContainer.getElement().setAttribute("file", "");
			size.getElement().setAttribute("size", "");
			container.getElement().getStyle().setProperty("margin", "0 0 0 -20px");
		}

		public void showAttachmentInfo(AttachmentInfo data) {

		}

		@Override
		public HandlerRegistration addOpenHandler(OpenHandler<AttachmentIdentity> handler) {
			return addHandler(handler, OpenEvent.getType());
		}

		@Override
		public void onValueChange(ValueChangeEvent<Boolean> event) {
			// Get attachment identity
			AttachmentIdentity file = (AttachmentIdentity) ((Widget) event.getSource()).getLayoutData();

			// Add or remove file
			if (event.getValue() == true)
				selectedFiles.add(file.getId());
			else
				selectedFiles.remove(file.getId());
		}

		@Override
		public void onClick(ClickEvent event) {
			Object source = event.getSource();

			if (source.equals(downAll)) {
				// Open new window
				Window.open(GWT.getHostPageBaseURL() + "attachment/full/"
						+ document.getIdentity().getId() + "/" + document.getIdentity().getName()
						+ "(full).zip", "downFiles", "");
			} else if (source.equals(downSelected)) {
				if (selectedFiles.size() > 0) {
					String href = GWT.getHostPageBaseURL() + "attachment/multiple/"
							+ document.getIdentity().getId() + "/";
					String files = "";

					for (String id : selectedFiles) {
						files += id + ",";
					}

					Window.open(href + CharBase64.encodeWebSafe(files.getBytes(), true) + "/"
							+ document.getIdentity().getName() + ".zip", "downFiles", "");
				}
			} else {
				// Get attachment identity
				AttachmentIdentity file = (AttachmentIdentity) ((Widget) source).getLayoutData();

				// Open new window
				Window.open(GWT.getHostPageBaseURL() + "attachment/single/"
						+ document.getIdentity().getId() + "/" + file.getId() + "/" + file.getName(),
						"downFiles", "");
			}
		}
	}

	private final ToweeConstants constants;
	private final ClientResource resource;

	private final RevisionSelector revSelector;
	private final Button editButton;

	// Document fields
	private SpanPanel author = new SpanPanel();
	private SpanPanel name = new SpanPanel();
	private SpanPanel text = new SpanPanel();
	private FlowPanel profile = new FlowPanel();
	private FlowPanel date = new FlowPanel();

	private final AttachmentsContainer attContainer;
	private final FlowPanel discussion;
	private final FlowPanel path;
	private final LoadingBar loading;

	private DocumentData document;

	@Inject
	public RepositoryDocumentView(ToweeConstants constants, ClientResource resource) {
		this.constants = constants;
		this.resource = resource;

		this.revSelector = new RevisionSelector();
		this.attContainer = new AttachmentsContainer();
		this.editButton = new Button();
		this.loading = new LoadingBar(resource);

		this.discussion = new FlowPanel();
		this.path = new FlowPanel();

		createPathContainer();
		createDocument();
		createDiscussion();
	}

	private void createDiscussion() {
		add(discussion);
	}

	private void createDocument() {
		// Create relative container
		FlowPanel container = new FlowPanel();
		container.setStyleName(resource.css().repoDocumentContainer());
		add(container);

		// Create revision selector
		container.add(revSelector);

		// Create name
		FlowPanel nameContainer = new FlowPanel();
		nameContainer.add(name);
		nameContainer.setStyleName(resource.css().repoDocumentNameContainer());
		container.add(nameContainer);

		// Create edit button
		editButton.setStyleName(resource.css().repoDocumentEditButton());
		container.add(editButton);

		// Create time machine button
		Button timeMachine = new Button();
		timeMachine.setStyleName(resource.css().repoDocumentTimeMachineButton());
		container.add(timeMachine);
		timeMachine.addClickHandler(new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {
				revSelector.show();
			}
		});

		// Create author and date
		FlowPanel info = new FlowPanel();
		info.getElement().setAttribute("info", "");
		info.add(new SpanPanel(constants.author()));
		info.add(author);
		info.add(new SpanPanel(constants.by()));
		info.add(date);
		container.add(info);
		author.getElement().setAttribute("author", "");

		// Create text
		FlowPanel textContainer = new FlowPanel();
		textContainer.add(attContainer);
		textContainer.add(profile);
		textContainer.add(text);
		profile.setStyleName(resource.css().repoDocumentProfile());
		textContainer.getElement().setAttribute("text", "");
		container.add(textContainer);

	}

	private void createPathContainer() {
		path.setStyleName(resource.css().repoNodeSelectorContainer());
		add(path);
	}

	private Widget createProfileField(String name, String value) {
		FlowPanel area = new FlowPanel();

		SpanPanel keyContainer = new SpanPanel(name + ":");
		keyContainer.getElement().setAttribute("key", "");

		SpanPanel valContainer = new SpanPanel(value);
		valContainer.getElement().setAttribute("value", "");

		area.add(keyContainer);
		area.add(valContainer);
		return area;
	}

	@Override
	public void showDocument(DocumentData doc, boolean canEdit) {
		this.document = doc;

		// Set document fields
		name.setText(doc.getIdentity().getName());
		author.setText(doc.getIdentity().getAuthor().getFullName());
		date.clear();
		date.add(DataHelpers.formattedDateTimeWidget(doc.getIdentity().getUpdateDate()));
		text.setHTML(doc.getDescription());

		// Set profile
		profile.clear();
		for (ProfileArea area : doc.getProfile().getProfileAreas()) {
			profile.add(createProfileField(area.getName(), area.getValue()));
		}

		// Set attachments
		attContainer.clear();
		if (doc.getAttachments().getAttachmentList().size() == 0) {
			attContainer.setVisible(false);
		} else {
			attContainer.setVisible(true);
			for (AttachmentIdentity att : doc.getAttachments().getAttachmentList()) {
				attContainer.addAttachment(att);
			}
		}

		// Set revisions
		revSelector.clear();
		for (DocumentDiff diff : doc.getDiffs()) {
			revSelector.addRevision(diff);
		}

		// Set editable
		editButton.setVisible(canEdit);
	}

	@Override
	public void showAttachmentData(AttachmentInfo data) {
		attContainer.showAttachmentInfo(data);
	}

	@Override
	public void selectRevision(int revision) {
		revSelector.selectRevision(revision);
	}

	@Override
	public void onDocumentLoading() {
		loading.toggle();
	}

	@Override
	public void onDocumentLoaded() {
		loading.toggle();
	}

	@Override
	public HasSelectionHandlers<Integer> getRevisionSelector() {
		return revSelector;
	}

	@Override
	public HasOpenHandlers<AttachmentIdentity> getAttachmentOpenner() {
		return attContainer;
	}

	@Override
	public HasClickHandlers getDocumentEditorButton() {
		return editButton;
	}

	@Override
	public HasWidgets getDiscussinoContainer() {
		return discussion;
	}

	@Override
	public HasWidgets getPathSelectorContainer() {
		return path;
	}
}